package {
	import flare.animate.FunctionSequence;
	import flare.animate.Parallel;
	import flare.animate.Sequence;
	import flare.animate.TransitionEvent;
	import flare.animate.Tween;
	import flare.display.RectSprite;
	import flare.util.Arrays;
	import flare.util.Colors;
	
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	
	public class DividedRectangle extends RectSprite
	{	
		public var fullRects:Array;
		public var currentValues:Array;
		public var nextValues:Array;
		public var areaValue:Number;
		
		public var _width:Number;
		public var _height:Number;
		public var _topInset:Number;
		public var _margin:Number;
		
		public var nameValueCount:Object;
		
		public var previousRect:Rectangle;
		public var rect:Rectangle;
		public var displayRect:Rectangle;
		
		public var finalColor:uint;
		
		public var callbackFunctions:Array;
		public var callbackObjects:Array;
		public var shiftCallbackFunctions:Array;
		public var shiftCallbackObjects:Array;
		
		public var parentCanvas:Canvas;
		public var parentRect:DividedRectangle;
		public var highlighted:Boolean;
		
		public static var HIGHLIGHT_FACTOR:Number = 1.4;
		
		public static const DEFAULT_MOVE_DURATION:Number = 0.7;
		public static const DEFAULT_FADE_DURATION:Number = 0.1;
		public static const RIGHT_LABEL_LENGTH:Number =  30.0;
		
		public function toDisplayRect(duration:Number = 1.0):Sequence
		{
			return toRect(displayRect, duration);
		}
		
		public function toOriginalRect(duration:Number = 1.0):Sequence
		{
			return toRect(rect, duration);
		}
		
		public function toRectDidFinish(evt:Event):void
		{
			
		}
		
		
		public function toRect(theRect:Rectangle, duration:Number = 1.0):Sequence
		{
			var allTween:Tween = new Tween(this, duration, {x:theRect.origin.x, y:theRect.origin.y, w:theRect.width, h:theRect.height});
			allTween.addEventListener(TransitionEvent.END, this.toRectDidFinish);
			
			return new Sequence(allTween);
		}
		
		public function bringToFront():void
		{
			if(parentRect != null)
			{
				parentRect.setChildIndex(this, parent.numChildren - 1);
				parentRect.bringToFront();
			}
		}	
		  
        private function makeRectsInRect(boundRect:Rectangle, values:Array):Array
        {
        	var rects:Array = new Array();
        	var sum:Number = 0;
        	
        	for(var k:Number = 0; k < values.length; k++)
        	{
        		rects.push(new DividedRectangle(this.w, this.h, _topInset, _margin, parentCanvas, this, values[k]));
        		rects[k].fillColor = this.fillColor;
        		rects[k].finalColor = colorForInt(k);
        		sum += rects[k].nameValueCount.count;
        	}      	
        	
        	var area:Number = boundRect.width*boundRect.height;
        	
        	for(var j:Number = 0; j < rects.length; j++)
        	{
        		rects[j].areaValue = area*(rects[j].nameValueCount.count/sum);
        	}
        	
        	return rects;
        }
        
        private function placeRectsInRect(rects:Array, boundRect:Rectangle):void
        {
        	var remainingRect:Rectangle = new Rectangle(0, 0, boundRect.width, boundRect.height);
        		
        	squarify(rects, new Array(), availableWidth(boundRect), remainingRect);
        }
        
        private function availableWidth(theRect:Rectangle):Number
        {
        	return Math.min(theRect.width, theRect.height);
        }
        
        public function shiftToRect(newRect:Rectangle, duration:Number):void
        {	
        	bringToFront();
        	
        	this.previousRect = this.displayRect;
        	
        	var parentShift:Sequence = this.toRect(newRect, duration);
            var childrenShift:Parallel = new Parallel();
            childrenShift.add(parentShift);
        	
        	setDimensions(newRect.width, newRect.height, _topInset, _margin);
        	
        	if(this.hasAnyData())
        	{
	        	var boundingRect:Rectangle = new Rectangle(0, 0, _width, _height);
	            var allRects:Array = makeRectsInRect(boundingRect, currentValues);
	            placeRectsInRect(allRects, boundingRect);
	            
	            var fullRect:Rectangle = new Rectangle(0, 0, newRect.width, newRect.height);
	            var theFullRects:Array = makeRectsInRect(fullRect, currentValues);
	            placeRectsInRect(theFullRects, fullRect);
            
	            for(var i:Number = 0; i < theFullRects.length; i++)
	            {
	            	var theFullRect:DividedRectangle = fullRects[i];
	            	var theSmallRect:DividedRectangle = allRects[i];
	            	
	            	theFullRect.displayRect = new Rectangle(theSmallRect.rect.origin.x + 2*_margin, theSmallRect.rect.origin.y + 2*_margin + _topInset, theSmallRect.rect.width - 2*_margin, theSmallRect.rect.height - 2*_margin);
	            	childrenShift.add(fullRects[i].toRect(theFullRect.displayRect, duration));
	            }
	         }
            
            childrenShift.addEventListener(TransitionEvent.END, this.shiftDidEnd);
            childrenShift.play();
        }
        
        private function layoutRowInRect(row:Array, boundRect:Rectangle):void
        {
        	var vertical:Boolean = boundRect.width > boundRect.height;
        	var totalWidth:Number = availableWidth(boundRect);
        	
        	var areaSum:Number = 0;
        	
        	for(var i:Number = 0; i < row.length; i++)
        	{
        		areaSum += row[i].areaValue;
        	}
        	
        	var origin:Point = new Point(boundRect.origin.x, boundRect.origin.y + boundRect.height);
        	var length:Number = 0;
        	
        	for(var j:Number = 0; j < row.length; j++)
        	{
        		var percentArea:Number = row[j].areaValue/areaSum;
        		var currentWidth:Number = totalWidth*percentArea;
        		length = row[j].areaValue/currentWidth;
        		
        		if(vertical)
        		{
        			row[j].rect.origin.x = origin.x;
        			row[j].rect.origin.y = origin.y - currentWidth;
        			row[j].rect.height = currentWidth;
        			row[j].rect.width = length;
        			
        			origin.y -= currentWidth;
        		}else{
        			row[j].rect.origin.x = origin.x;
        			row[j].rect.origin.y = origin.y - length;
        			row[j].rect.height = length;
        			row[j].rect.width = currentWidth;
        			
        			origin.x += currentWidth;
        		}
        	}
        	
        	if(vertical)
        	{
        		boundRect.origin.x += length;
        		boundRect.width -= length;
        	}else{
        		boundRect.height -= length;
        	}

        }
        
        private function squarify(rects:Array, row:Array, width:Number, boundRect:Rectangle):void
        {
        	if(rects.length > 0)
        	{
        		var curRect:DividedRectangle = rects[0];
        		var otherRow:Array = Arrays.copy(row);
        		otherRow.push(curRect);
        		
        		if(worst(row, width) >= worst(otherRow, width) || row.length == 0)
        		{
        			var tail:Array = Arrays.copy(rects);
        			Arrays.remove(tail, curRect);
        			squarify(tail, otherRow, width, boundRect);
        		}else{
        			layoutRowInRect(row, boundRect);
        			squarify(rects, new Array(), availableWidth(boundRect), boundRect);
        		}
        	}else{
        		layoutRowInRect(row, boundRect);
        	}
        }
        
        //assumes these rects are sorted with biggest datum first
        private function worst(rects:Array, width:Number):Number
        {
        	if(rects.length == 0)
        	{
        		return 0;
        	}
        	
        	var sum:Number = 0;
        	
        	for(var i:Number = 0; i < rects.length; i++)
        	{
        		sum += rects[i].areaValue;
        	}
        	
        	var maxValue:Number = rects[0].areaValue;
        	var minValue:Number = rects[rects.length - 1].areaValue;
        	      	
        	var result:Number = Math.max((width*width*maxValue)/(sum*sum), (sum*sum)/(width*width*minValue));

        	return result;
        }
        
        public function showNewValues():Sequence
        {
        	currentValues = nextValues;
        	
        	prepForData(nextValues);
        	return drawRectsInContainer(fullRects, this);
        }
        
        public function switchRects(seq:Sequence):void
        {
        	for(var i:Number = 0; i < fullRects.length; i++)
        	{
        		this.removeChild(fullRects[i]);
        	}
        	
        	fullRects = null;
        	
        	
        	seq.reset();
        	seq = showNewValues();
        }
        
        public function prepForData(values:Array):void
        {
        	setDimensions(width, height, _topInset, _margin);
        	var boundingRect:Rectangle = new Rectangle(0, 0, _width, _height);
            var allRects:Array = makeRectsInRect(boundingRect, values);
            placeRectsInRect(allRects, boundingRect);
            
            var fullRect:Rectangle = new Rectangle(0, 0, width, height);
            fullRects = makeRectsInRect(fullRect, values);
            placeRectsInRect(fullRects, fullRect);
            
            for(var i:Number = 0; i < fullRects.length; i++)
            {
            	var theFullRect:DividedRectangle = fullRects[i];
            	var theSmallRect:DividedRectangle = allRects[i];
            	
            	theFullRect.displayRect = new Rectangle(theSmallRect.rect.origin.x + 2*_margin, theSmallRect.rect.origin.y + 2*_margin + _topInset, theSmallRect.rect.width - 2*_margin, theSmallRect.rect.height - 2*_margin);
            }
        }
        
        public function adjustToData(nameValueCounts:Array):Sequence
        {
        	this.nextValues = nameValueCounts;
        	
        	if(this.hasAnyData())
        	{
            	var reformer:FunctionSequence = reform();
            	reformer.push(this.switchRects, new Sequence());
            	return reformer;
         	}else{
         		return showNewValues();
         	}
        }
        
        public function didReform(event:Event):void
        {
        	fullRects = null;
        }
        
        public function reform():FunctionSequence
        {
        	var result:FunctionSequence = new FunctionSequence();
        	
        	if(this.hasAnyData())
        	{
        		if(this.areChildrenDivided())
        		{
        			var childReform:Parallel = new Parallel();
        			for(var i:Number = 0; i < fullRects.length; i++)
        			{
        				childReform.add(fullRects[i].reform());
        			}
        			
        			result.add(childReform);
        		}
        	
        		result.add(new Parallel(growAll(DEFAULT_MOVE_DURATION), decolorizeAll(DEFAULT_MOVE_DURATION)));
        		result.add(fadeOutAll(DEFAULT_FADE_DURATION));
        	}
        	
        	result.addEventListener(TransitionEvent.END, this.didReform);
        	
        	return result;
        }
        
        public function splitDidEnd(theEvent:TransitionEvent):void
        {
        	for(var i:Number = 0; i < callbackFunctions.length; i++)
        	{
        		callbackFunctions[i].call(callbackObjects[i]);
        	}
        }
        
        public function shiftDidEnd(theEvent:TransitionEvent):void
        {
        	for(var i:Number = 0; i < shiftCallbackFunctions.length; i++)
        	{
        		shiftCallbackFunctions[i].call(shiftCallbackObjects[i]);
        	}
        }
        
        public function addCallbackForSplitEnd(theObj:Object, theFunc:Function):void
        {
        	callbackFunctions.push(theFunc);
        	callbackObjects.push(theObj);
        }
        
        public function removeCallbackForSplitEnd(theObj:Object, theFunc:Function):void
        {
        	Arrays.remove(callbackFunctions, theFunc);
        	Arrays.remove(callbackObjects, theObj);
        }
        
        public function addShiftCallback(theObj:Object, theFunc:Function):void
        {
        	shiftCallbackFunctions.push(theFunc);
        	shiftCallbackObjects.push(theObj);
        }
        
        public function removeShiftCallback(theObj:Object, theFunc:Function):void
        {
        	Arrays.remove(shiftCallbackFunctions, theFunc);
        	Arrays.remove(shiftCallbackObjects, theObj);
        }

        public function drawRectsInContainer(rects:Array, container:Sprite):Sequence
        {
        	for(var i:Number = 0; i < rects.length; i++)
            {
            	var thisRect:DividedRectangle = rects[i];
            	
            	thisRect.x = thisRect.rect.origin.x;
            	thisRect.y = thisRect.rect.origin.y;
            	thisRect.w = thisRect.rect.width;
            	thisRect.h = thisRect.rect.height;
            	
            	thisRect.alpha = 0.0
            	
            	container.addChild(thisRect);          	
            }
            
            var split:Sequence = splitUp();
            split.addEventListener(TransitionEvent.END, this.splitDidEnd);
            return split;
        }
        
        public function splitUp():Sequence
        {
        	
        	var splitUp:Sequence = new Sequence();
            
            splitUp.add(fadeInAll(DEFAULT_FADE_DURATION));
            splitUp.add(new Parallel(shrinkAll(DEFAULT_MOVE_DURATION), colorizeAll(DEFAULT_MOVE_DURATION)));
            
            return splitUp;
        }
        
        public function shrinkAll(duration:Number):Parallel
        {
        	var shrink:Parallel = new Parallel();
            
            for(var j:Number = 0; j < fullRects.length; j++)
            {
            	var theRect:DividedRectangle = fullRects[j];
            	
            	shrink.add(theRect.toDisplayRect(duration));
            }
            
            return shrink;
        }
        
        public function rank():Number
        {
        	if(this.parentRect == null)
        		return 1;
        		
        	return parentRect.rank() + 1;
        }
        
        public function totalOffset():Point
        {
        	if(this.parentRect == null)
        		return new Point(0,0);
        		
        	var parentOffset:Point = this.parentRect.totalOffset();
        	
        	parentOffset.x += this.x;
        	parentOffset.y += this.y;
        	
        	return parentOffset;
        }
        
        public function colorizeAll(duration:Number):Parallel
        {
        	var colorize:Parallel = new Parallel();
            
            for(var j:Number = 0; j < fullRects.length; j++)
            {
            	var theRect:DividedRectangle = fullRects[j];
            	
            	colorize.add(new Tween(theRect, duration, {fillColor:theRect.finalColor}));
            }
            
            return colorize;
        }
        
        public function decolorizeAll(duration:Number):Parallel
        {
        	var decolorize:Parallel = new Parallel();
            
            for(var j:Number = 0; j < fullRects.length; j++)
            {
            	var theRect:DividedRectangle = fullRects[j];
            	
            	decolorize.add(new Tween(theRect, duration, {fillColor:this.fillColor}));
            }
            
            return decolorize;
        }
        
        public function highlightColor():uint
        {
        	return Colors.rgba(HIGHLIGHT_FACTOR*Colors.r(finalColor), HIGHLIGHT_FACTOR*Colors.g(finalColor), HIGHLIGHT_FACTOR*Colors.b(finalColor), Colors.a(finalColor));
        }
        
        public function growAll(duration:Number):Parallel
        {
        	var grow:Parallel = new Parallel();
            
            for(var j:Number = 0; j < fullRects.length; j++)
            {
            	var theRect:DividedRectangle = fullRects[j];
            	
            	grow.add(theRect.toOriginalRect(duration));
            }
            
            return grow;
        }
        
        public function colorForInt(integer:Number, theParent:DividedRectangle = null):uint
        {
        	var mod:Number = integer%13;
        	if(mod == 0)
        	{
        		return Colors.rgba(120, 50, 50, 255);
        	}else if(mod == 1)
        	{
        		return Colors.rgba(50, 120, 50, 255);
        	}else if(mod == 2)
        	{
        		return Colors.rgba(50, 50, 120, 255);
        	}else if(mod == 3)
        	{
        		return Colors.rgba(120, 120, 50, 255);
        	}else if(mod == 4)
        	{
        		return Colors.rgba(120, 50, 120, 255);
        	}else if(mod == 5)
        	{
        		return Colors.rgba(50, 120, 120, 255);
        	}else if(mod == 6)
        	{
        		return Colors.rgba(120, 50, 160, 255);
        	}else if(mod == 7)
        	{
        		return Colors.rgba(120, 100, 70, 255);
        	}else if(mod == 8)
        	{
        		return Colors.rgba(70, 120, 100, 255);
        	}else if(mod == 9)
        	{
        		return Colors.rgba(100, 70, 120, 255);
        	}else if(mod == 10)
        	{
        		return Colors.rgba(130, 70, 100, 255);
        	}else if(mod == 11)
        	{
        		return Colors.rgba(100, 130, 70, 255);
        	}else if(mod == 12)
        	{
        		return Colors.rgba(70, 100, 130, 255);
        	}
        	
        	return Colors.rgba(50, 200, 75, 255);
        }
        
        public function fadeOutAll(duration:Number):Parallel
        {
        	var fadeOut:Parallel = new Parallel;
        	
        	for(var j:Number = 0; j < fullRects.length; j++)
            {
            	var theRect:DividedRectangle = fullRects[j];
            	
            	fadeOut.add(new Tween(theRect, duration, {alpha:0.0}));
            }
            
            return fadeOut;
        
        }
        
        public function fadeInAll(duration:Number):Parallel
        {
        	var fadeIn:Parallel = new Parallel;
        	
        	for(var j:Number = 0; j < fullRects.length; j++)
            {
            	var theRect:DividedRectangle = fullRects[j];
            	
            	fadeIn.add(new Tween(theRect, duration, {alpha:1.0}));
            }
            
            return fadeIn;
        
        }
        
        public function setDimensions(width:Number, height:Number, topInset:Number, margin:Number):void
        {
        	_topInset = topInset;
        	_margin = margin;
        	
        	if(_topInset > 0.3*height)
        		_topInset = 0.1*height;
        		
        	if(_margin > 0.1*Math.min(width, height))
        		_margin = 0.05*Math.min(width, height);
        	
        	_width = width - 2*_margin;
        	_height = height - _topInset - 2*_margin;
        }
        
        public function stringForRightLabel():String
        {
        	if(nameValueCount == null || parentRect == null)
        		return "";
        		
        	var countString:String = (100*(this.nameValueCount.count/parentRect.nameValueCount.count)).toString();
        	   	
        	var result:String =  countString.substr(0,3);
        	
        	if(result.length == 3)
        	{
        		if(result.substr(2,1) == ".")
        		{
        			result = result.substr(0,2);
        		}else if(result.substr(1,1) == ".")
        		{
        			result = result.substr(0, 1);
        		}
        	}
        	
        	return result + "%";
        }
        
        public function stringForLeftLabel():String
        {
        	if(nameValueCount == null)
        		return "";
        		
        	var valueString:String = nameValueCount.value;
        	
        	if(this.hasAnyData())
        	{
        		var oneChild:DividedRectangle = this.fullRects[0];
        		if(oneChild.nameValueCount != null && oneChild != null)
        		{
        			valueString += " by " + oneChild.nameValueCount.name;
        		}
        	}
        	
        	return valueString;
        }
        
        public function CustomDividedRectangle(width:Number, height:Number, topInset:Number, margin:Number):DividedRectangle
        {
        	callbackFunctions = new Array();
        	callbackObjects = new Array();
        	shiftCallbackFunctions = new Array();
        	shiftCallbackObjects = new Array();
        	
        	
        	this.rect = new Rectangle(0,0,0,0);

        	this.w = width;
        	this.h = height;
        	this.x = 0;
        	this.y = 0;
        	this.fillColor = Colors.rgba(100, 100, 200, 255);
        	this.lineColor = Colors.rgba(0, 0, 0, 255);
        	this.lineWidth = 1.0;
        	
        	setDimensions(width, height, topInset, margin);      
 			
            return this;
        }
        
        
        public function hasAnyData():Boolean
        {
        	return fullRects != null;
        }
        
        public function gotDubClicked(theEvent:Event):void
        {
        	parentCanvas.rectGotClicked(this);
        }
        
        public function hasHighlightedParent():Boolean
        {
        	if(parentRect == null)
        		return false;
        		
        	return parentRect.highlighted;
        }
        
        public function hasHighlightedChild():Boolean
        {
        	if(fullRects == null)
        		return false;
        	
        	for(var i:Number = 0; i < fullRects.length; i++)
        	{
        		if(fullRects[i].highlighted)
        		{
        			return true;
        		}
        	}
        	
        	for(var j:Number = 0; j < fullRects.length; j++)
        	{
        		if(fullRects[j].hasHighlightedChild())
        		{
        			return true;
        		}
        	}
        	
        	return false;
        }
        
        public function mouseOvered(theEvent:Event):void
        {
        		if(!this.hasHighlightedChild())
        		{
	        		this.highlighted = true;
	        		if(this != this.parentCanvas.rootRect)
	        		{
	        			this.fillColor = this.highlightColor();
	        		}
	        		
	        		this.parentCanvas.updateSentenceForRect(this);
        		}
        }
        
        public function mouseOuted(theEvent:Event):void
        {
    		this.highlighted = false;
    		this.fillColor = this.finalColor;     	
        }
        
        public function areChildrenDivided():Boolean
        {
        	if(this.hasAnyData())
        	{
        		return fullRects[0].hasAnyData();
        	}
        	
        	return false;
        }
        
        public function parentValues():Array
        {
        	if(this == parentCanvas.rootRect)
        	{
        		return new Array();
        	}
        	
        	var parents:Array = this.parentRect.parentValues();
        	
        	if(nameValueCount != null)
        	{
        		parents.push(nameValueCount);
        	}
        	
        	return parents;
        }
        
        public function parentString():String
        {
        	var parents:Array = this.parentValues();
        	
        	if(parents.length == 0)
        		return "";
        		
        	parents.pop();
        	
        	var resultString:String = "";
        	
        	for(var i:Number = 0; i < parents.length; i++)
        	{
        		if(i >0)
        		{
        			resultString += ", ";
        		}
        		
        		resultString += parents[i].name + " = " + parents[i].value;
        	}
        	
        	return resultString;
        }
        
		public function DividedRectangle(width:Number = 500.0, height:Number = 400.0, topInset:Number = 5.0, margin:Number = 3.0, canvas:Canvas = null, theParent:DividedRectangle = null, description:Object = null)
		{	
			super();
			
			parentCanvas = canvas;
			parentRect = theParent;
			nameValueCount = description;
			
			this.addEventListener(MouseEvent.CLICK, this.gotDubClicked);

			this.addEventListener(MouseEvent.MOUSE_OVER, this.mouseOvered);
			this.addEventListener(MouseEvent.MOUSE_OUT, this.mouseOuted);
			
			CustomDividedRectangle(width, height, topInset, margin);
		}
	}
}
